﻿using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq.Expressions;
using Loki.Commands;
using Loki.Events;

namespace Loki.Utils
{
    /// <summary>
    /// Loki change management service
    /// </summary>
    public class LokiEventService : LokiService, IEventService
    {
        private WeakNotifyChangedManager _NotifyPropertyChangeManager;
        private WeakEventManager<INotifyCanExecuteChanged, EventArgs> _CanExecuteChangedManager;
        private WeakEventManager<ICentralizedChangeTracking, EventArgs> _CentralizedChangeManager;
        private WeakEventManager<INotifyPropertyChanged, PropertyChangedEventArgs> _NotifyChangeManager;
        private WeakEventManager<INotifyCollectionChanged, NotifyCollectionChangedEventArgs> _CollectionChangedManager;

        /// <summary>
        /// Initializes a new instance of the event service.
        /// </summary>
        LokiEventService()
        {
            _NotifyPropertyChangeManager = new WeakNotifyChangedManager();
            _CentralizedChangeManager = new WeakEventManager<ICentralizedChangeTracking, EventArgs>(StateChangedMapper, StateChangedUnmapper);
            _NotifyChangeManager = new WeakEventManager<INotifyPropertyChanged, PropertyChangedEventArgs>(ChangedMapper, ChangedUnmapper);
            _CollectionChangedManager = new WeakEventManager<INotifyCollectionChanged, NotifyCollectionChangedEventArgs>(CollectionChangedMapper, CollectionChangedUnmapper);
            _CanExecuteChangedManager = new WeakEventManager<INotifyCanExecuteChanged, EventArgs>(CanExecuteChangedMapper, CanExecuteChangedUnmapper);
        }

        #region Cleaning
        /// <summary>
        /// Removes the collected entries.
        /// </summary>
        public void RemoveCollectedEntries()
        {
            _NotifyPropertyChangeManager.RemoveCollectedEntries();
            _CentralizedChangeManager.RemoveCollectedEntries();
            _NotifyChangeManager.RemoveCollectedEntries();
            _CollectionChangedManager.RemoveCollectedEntries();
            _CanExecuteChangedManager.RemoveCollectedEntries();
        }

        /// <summary>
        /// Releases the unmanaged resources used by an instance of the <see cref="LokiService"/> class and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing"><strong>true</strong> to release both managed and unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _NotifyPropertyChangeManager.Dispose();
                _CentralizedChangeManager.Dispose();
                _NotifyChangeManager.Dispose();
                _CollectionChangedManager.Dispose();
                _CanExecuteChangedManager.Dispose();
            }
            base.Dispose(disposing);
        }
        #endregion

        #region Metadata
        /// <summary>
        /// Gets the service name
        /// </summary>
        /// <value>
        /// The service name.
        /// </value>
        public override string Name
        {
            get { return "Loki changes event manager"; }
        }

        /// <summary>
        /// Gets the service identifier.
        /// </summary>
        /// <value>
        /// The service identifier.
        /// </value>
        public override Guid ID
        {
            get { return new Guid("{5A463D59-FBE8-43a7-8BA5-EDBFF9AF0A5B}"); }
        }
        #endregion

        #region Property changed tracking management
        /// <summary>
        /// Registers the specified INotifyPropertyChanged handler.
        /// </summary>
        /// <typeparam name="TListener">The type of the listener.</typeparam>
        /// <param name="P_Source">The event source.</param>
        /// <param name="P_PropertyName">Name of the property.</param>
        /// <param name="P_Listener">The event listener.</param>
        /// <param name="P_PropertyChangedCallback">The  property changed callback.</param>
        public void RegisterPropertyChanged<TSource, TListener>(TSource P_Source, Expression<Func<TSource, object>> P_Property, TListener P_Listener, Action<TListener, object, PropertyChangedEventArgs> P_PropertyChangedCallback) where TSource : INotifyPropertyChanged
        {
            RegisterPropertyChanged<TSource, TListener>(P_Source, ExpressionHelper.Reflect<TSource>.GetProperty(P_Property).Name, P_Listener, P_PropertyChangedCallback);
        }

        public void RegisterPropertyChanged<TSource, TListener>(TSource P_Source, string P_PropertyName, TListener P_Listener, Action<TListener, object, PropertyChangedEventArgs> P_PropertyChangedCallback) where TSource : INotifyPropertyChanged
        {
            _NotifyPropertyChangeManager.Register<TListener>(P_Source, P_PropertyName, P_Listener, P_PropertyChangedCallback);
        }

        /// <summary>
        /// Unregisters the specified INotifyPropertyChanged handler.
        /// </summary>
        /// <param name="P_Source">The event source.</param>
        /// <param name="P_PropertyName">Name of the property.</param>
        /// <param name="P_Listener">The event listener.</param>
        public void UnregisterPropertyChanged<TSource>(TSource P_Source, Expression<Func<TSource, object>> P_Property, object P_Listener) where TSource : INotifyPropertyChanged
        {
            _NotifyPropertyChangeManager.Unregister(P_Source, ExpressionHelper.Reflect<TSource>.GetProperty(P_Property).Name, P_Listener);
        }

        /// <summary>
        /// Unregisters the source.
        /// </summary>
        /// <param name="P_Source">The p_ source.</param>
        public void UnregisterPropertyChangedSource<TSource>(TSource P_Source) where TSource : INotifyPropertyChanged
        {
            _NotifyPropertyChangeManager.UnregisterSource(P_Source);
        }
        #endregion

        #region Changed tracking management

        private static void ChangedMapper(INotifyPropertyChanged P_Source, WeakEventBridge<INotifyPropertyChanged, PropertyChangedEventArgs> P_Bridge)
        {
            P_Source.PropertyChanged += P_Bridge.OnEvent;
        }

        private static void ChangedUnmapper(INotifyPropertyChanged P_Source, WeakEventBridge<INotifyPropertyChanged, PropertyChangedEventArgs> P_Bridge)
        {
            LokiServices.Event.UnregisterChangedSource(P_Source);
            P_Source.PropertyChanged -= P_Bridge.OnEvent;
        }

        /// <summary>
        /// Registers the specified INotifyPropertyChanged handler.
        /// </summary>
        /// <typeparam name="TListener">The type of the listener.</typeparam>
        /// <param name="P_Source">The event source.</param>
        /// <param name="P_Listener">The event listener.</param>
        /// <param name="P_Callback">The callback.</param>
        public void RegisterChanged<TListener>(INotifyPropertyChanged P_Source, TListener P_Listener, Action<TListener, object, PropertyChangedEventArgs> P_Callback)
        {
            _NotifyChangeManager.Register<TListener>(P_Source, P_Listener, P_Callback);
        }

        /// <summary>
        /// Unregisters the specified INotifyPropertyChanged handler.
        /// </summary>
        /// <param name="P_Source">The event source.</param>
        /// <param name="P_Listener">The event listener.</param>
        public void UnregisterChanged(INotifyPropertyChanged P_Source, object P_Listener)
        {
            _NotifyChangeManager.Unregister(P_Source, P_Listener);
        }

        /// <summary>
        /// Unregisters the source.
        /// </summary>
        /// <param name="P_Source">The source.</param>
        public void UnregisterChangedSource(INotifyPropertyChanged P_Source)
        {
            _NotifyChangeManager.UnregisterSource(P_Source);
        }
        #endregion

        #region State changed tracking management

        private static void StateChangedMapper(ICentralizedChangeTracking P_Source, WeakEventBridge<ICentralizedChangeTracking, EventArgs> P_Bridge)
        {
            P_Source.StateChanged += P_Bridge.OnEvent;
        }

        private static void StateChangedUnmapper(ICentralizedChangeTracking P_Source, WeakEventBridge<ICentralizedChangeTracking, EventArgs> P_Bridge)
        {
            LokiServices.Event.UnregisterStateChangedSource(P_Source);
            P_Source.StateChanged -= P_Bridge.OnEvent;
        }

        /// <summary>
        /// Registers the specified INotifyPropertyChanged handler.
        /// </summary>
        /// <typeparam name="TListener">The type of the listener.</typeparam>
        /// <param name="P_Source">The event source.</param>
        /// <param name="P_Listener">The event listener.</param>
        /// <param name="P_Callback">The callback.</param>
        public void RegisterStateChanged<TListener>(ICentralizedChangeTracking P_Source, TListener P_Listener, Action<TListener, object, EventArgs> P_Callback)
        {
            _CentralizedChangeManager.Register<TListener>(P_Source, P_Listener, P_Callback);
        }

        /// <summary>
        /// Unregisters the specified INotifyPropertyChanged handler.
        /// </summary>
        /// <param name="P_Source">The event source.</param>
        /// <param name="P_Listener">The event listener.</param>
        public void UnregisterStateChanged(ICentralizedChangeTracking P_Source, object P_Listener)
        {
            _CentralizedChangeManager.Unregister(P_Source, P_Listener);
        }

        /// <summary>
        /// Unregisters the source.
        /// </summary>
        /// <param name="P_Source">The source.</param>
        public void UnregisterStateChangedSource(ICentralizedChangeTracking P_Source)
        {
            _CentralizedChangeManager.UnregisterSource(P_Source);
        }
        #endregion

        #region Collection changed tracking management

        private static void CollectionChangedMapper(INotifyCollectionChanged P_Source, WeakEventBridge<INotifyCollectionChanged, NotifyCollectionChangedEventArgs> P_Bridge)
        {
            P_Source.CollectionChanged += P_Bridge.OnEvent;
        }

        private static void CollectionChangedUnmapper(INotifyCollectionChanged P_Source, WeakEventBridge<INotifyCollectionChanged, NotifyCollectionChangedEventArgs> P_Bridge)
        {
            LokiServices.Event.UnregisterCollectionChangedSource(P_Source);
            P_Source.CollectionChanged -= P_Bridge.OnEvent;
        }

        /// <summary>
        /// Registers the specified INotifyPropertyChanged handler.
        /// </summary>
        /// <typeparam name="TListener">The type of the listener.</typeparam>
        /// <param name="P_Source">The event source.</param>
        /// <param name="P_Listener">The event listener.</param>
        /// <param name="P_Callback">The callback.</param>
        public void RegisterCollectionChanged<TListener>(INotifyCollectionChanged P_Source, TListener P_Listener, Action<TListener, object, NotifyCollectionChangedEventArgs> P_Callback)
        {
            _CollectionChangedManager.Register<TListener>(P_Source, P_Listener, P_Callback);
        }

        /// <summary>
        /// Unregisters the specified INotifyPropertyChanged handler.
        /// </summary>
        /// <param name="P_Source">The event source.</param>
        /// <param name="P_Listener">The event listener.</param>
        public void UnregisterCollectionChanged(INotifyCollectionChanged P_Source, object P_Listener)
        {
            _CollectionChangedManager.Unregister(P_Source, P_Listener);
        }

        /// <summary>
        /// Unregisters the source.
        /// </summary>
        /// <param name="P_Source">The source.</param>
        public void UnregisterCollectionChangedSource(INotifyCollectionChanged P_Source)
        {
            _CollectionChangedManager.UnregisterSource(P_Source);
        }
        #endregion

        #region Commands CanExecute changed tracking management

        private static void CanExecuteChangedMapper(INotifyCanExecuteChanged P_Source, WeakEventBridge<INotifyCanExecuteChanged, EventArgs> P_Bridge)
        {
            P_Source.CanExecuteChanged += P_Bridge.OnEvent;
        }

        private static void CanExecuteChangedUnmapper(INotifyCanExecuteChanged P_Source, WeakEventBridge<INotifyCanExecuteChanged, EventArgs> P_Bridge)
        {
            LokiServices.Event.UnregisterCanExecuteChangedSource(P_Source);
            P_Source.CanExecuteChanged -= P_Bridge.OnEvent;
        }

        /// <summary>
        /// Registers the specified INotifyCanExecuteChanged handler.
        /// </summary>
        /// <typeparam name="TListener">The type of the listener.</typeparam>
        /// <param name="P_Source">The event source.</param>
        /// <param name="P_Listener">The event listener.</param>
        /// <param name="P_Callback">The callback.</param>
        public void RegisterCanExecuteChanged<TListener>(INotifyCanExecuteChanged P_Source, TListener P_Listener, Action<TListener, object, EventArgs> P_Callback)
        {
            _CanExecuteChangedManager.Register<TListener>(P_Source, P_Listener, P_Callback);
        }

        /// <summary>
        /// Unregisters the specified INotifyPropertyChanged handler.
        /// </summary>
        /// <param name="P_Source">The event source.</param>
        /// <param name="P_Listener">The event listener.</param>
        public void UnregisterCanExecuteChanged(INotifyCanExecuteChanged P_Source, object P_Listener)
        {
            _CanExecuteChangedManager.Unregister(P_Source, P_Listener);
        }

        /// <summary>
        /// Unregisters the source.
        /// </summary>
        /// <param name="P_Source">The source.</param>
        public void UnregisterCanExecuteChangedSource(INotifyCanExecuteChanged P_Source)
        {
            _CanExecuteChangedManager.UnregisterSource(P_Source);
        }
        #endregion
    }
}
